// This program is copyright VUW.
// You are granted permission to use it to construct your answer to a XMUT103 assignment.
// You may not distribute it in any other way without permission.

/* Code for XMUT103 - 2022T1, Assignment 2
 * Name:
 * Username:
 * ID:
 */

import ecs100.*;
import java.util.*;
import java.io.*;
import java.nio.file.*;

/**
 * WellingtonTrains
 * A program to answer queries about Wellington train lines and timetables for
 *  the train services on those train lines.
 *
 * See the assignment page for a description of the program and what you have to do.
 */

public class WellingtonTrains{
    //Fields to store the collections of Stations and Lines
    /*# YOUR CODE HERE */
    // Load the Station and Train Line data from the files into the data structures, 这是我们第一个任务
    // 所以在这里，我们选择一个Map来作为存储站点信息的数据结构，这个Map的Key是String，Value是对象Station
    // 之所以进行一个这样的设计，是因为在后面的编程中，我们可以单靠站点的名字（String类型）来从这个Map中取出对应的站点对象
    private Map<String, Station> stationMap;
    private Map<String, TrainLine> trainLineMap;

    // Fields for the suggested GUI.
    private String stationName;        // station to get info about, or to start journey from
    private String lineName;           // train line to get info about.
    private String destinationName;
    private int startTime = 0;         // time for enquiring about

    /**
     * main method:  load the data and set up the user interface
     */
    public static void main(String[] args){
        WellingtonTrains wel = new WellingtonTrains();
        wel.loadData();   // load all the data
        wel.setupGUI();   // set up the interface
        loadedData = true;
    }

    /**
     * Load data files
     */
    public void loadData(){
        //一开始打开你的项目文件夹，下面这三个方法肯定报错，因为没有给出这个方法的定义，所以我们需要写好这些函数的定义
        loadStationData();
        UI.println("Loaded Stations");
        loadTrainLineData();
        UI.println("Loaded Train Lines");
        // The following is only needed for the Completion and Challenge
        loadTrainServicesData();
        UI.println("Loaded Train Services");
    }
    /**
     * User interface has buttons for the queries and text fields to enter stations and train line
     * You will need to implement the methods here.
     */
    public void setupGUI(){
        //这些报错的也是同理，没有定义，我们要给出函数的定义，你可以不写具体内容，但是为了不让他们报错，也得先把函数定义写出来
        UI.addButton("All Stations",        this::listAllStations);
        UI.addButton("Stations by name",    this::listStationsByName);
        UI.addButton("All Lines",           this::listAllTrainLines);
        UI.addTextField("Station",          (String name) -> {this.stationName=name;});
        UI.addTextField("Train Line",       (String name) -> {this.lineName=name;});
        UI.addTextField("Destination",      (String name) -> {this.destinationName=name;});
        UI.addTextField("Time (24hr)",      (String time) ->
            {try{this.startTime=Integer.parseInt(time);}catch(Exception e){UI.println("Enter four digits");}});
        UI.addButton("Lines of Station",    () -> {listLinesOfStation(this.stationName);});
        UI.addButton("Stations on Line",    () -> {listStationsOnLine(this.lineName);});
        UI.addButton("Stations connected?", () -> {checkConnected(this.stationName, this.destinationName);});
        UI.addButton("Next Services",       () -> {findNextServices(this.stationName, this.startTime);});
        UI.addButton("Find Trip",           () -> {findTrip(this.stationName, this.destinationName, this.startTime);});

        UI.addButton("Quit", UI::quit);
        UI.setMouseListener(this::doMouse);

        UI.setWindowSize(900, 400);
        UI.setDivider(0.2);
        // this is just to remind you to start the program using main!
        if (! loadedData){
            UI.setFontSize(36);
            UI.drawString("Start the program from main", 2, 36);
            UI.drawString("in order to load the data", 2, 80);
            UI.sleep(2000);
            UI.quit();
        }
        else {
            UI.drawImage("data/geographic-map.png", 0, 0);
            UI.drawString("Click to list closest stations", 2, 12);
        }
    }

    public void doMouse(String action, double x, double y){
        if (action.equals("released")){
            /*# YOUR CODE HERE */

        }
    }

    // Methods for loading data and answering queries
    // 在这里写上面报错函数的定义，这样就不会报错了
    public void loadStationData(){
        // 把我们在实例变量那里弄的stationMap给实例化成HashMap，这是最常用的Map
        this.stationMap = new HashMap<>();
        // 在提示中我们可以知道，加载站点数据要通过读取data文件夹下的stations.data文件
        // 我们打开这个文件，随机抽取几行给大家找规律

        /*
        * Wellington 1 110 420
        * Ngauranga 3 136 389
        * Crofton-Downs 3 100 401
        * */

        // 再通过阅读station类，我们发现，构造一个station，需要提供参数，String name, int zone, double x, double y
        // 构造station的参数是4个，类型是字符串，数字（整数），数字（浮点），数字（浮点），而上面文件，每一行都是字符串，数字，数字，数字，
        // 所以说，我们通过读取stations.data文件，然后分割每一行的数据就能够构造起一个station了，真可谓是方法对了一切都是巧合
        try {
            // 用IO方法别忘了try&catch
            List<String> allLines = Files.readAllLines(Paths.get("data\\stations.data"));
            // 不过你肯定忘不了，因为不用try&catch，那就只能throw，不然程序编译不给通过:)

            for (String stationData : allLines) {
                // 因为我们的数据是一行一行读的，我们遍历每一行，在行上建立Scanner
                // 然后通过next，nextInt，nextDouble，nextDouble来建立station对象
                Scanner stringScanner = new Scanner(stationData);
                Station station = new Station(stringScanner.next(), stringScanner.nextInt(), stringScanner.nextDouble(), stringScanner.nextDouble());
                // 然后用名字对应station对象，放入到map中
                this.stationMap.put(station.getName(), station);
                // 你也可以用scanner先分别把名字，zone，x，y读到单独的变量中，然后构建station，最后put到map中

                /*
                String name = stringScanner.next();
                int zone = stringScanner.nextInt();
                double x = stringScanner.nextDouble();
                double y = stringScanner.nextDouble();
                Station station = new Station(name, zone, x, y);
                this.stationMap.put(name, station);
                */

                // 也可以不用Scanner，毕竟字符串处理方法有很多
                /*
                String[] pieceString = stationData.split(" "); //以空格做分割，这是找规律
                String name = pieceString[0]; // 最开始的是名字
                int zone = Integer.parseInt(pieceString[1]); // 然后是zone，但是类型不匹配，我们parse成int类型
                double x = Double.parseDouble(pieceString[2]);
                double y = Double.parseDouble(pieceString[3]);
                */
                // 后面省略
            }
        } catch (IOException exception){
            UI.print(""); //打印你想打印的错误信息
        }
    }

    public void loadTrainLineData(){
        // 加载火车线路，这个可能复杂了点
        // 首先我们要知道有多少条火车线路，这个数据在data文件夹下train-lines.data文件里，里面包含了火车线路信息
        // 然后，在<线路名>-stations.data文件里面，包含了这条火车线路途径的站点
        // 综上所述，我们的思路就是，先遍历train-lines中的每一行，这里每一行是一个trainline的名字，
        // 在进行每一行遍历的同时，我们读取这个线路的stations文件，把途经线路的station装入我们的trainline中，同时，途径线路也需要把trainline加入到自己的线路集
        // 而且写这个方法的时候还能够体会到为啥我们最开始要建立string和station的映射关系，以及它的好处
        trainLineMap = new HashMap<>();
        try {
            List<String> allLines = Files.readAllLines(Paths.get("data\\train-lines.data"));
            for (String line : allLines) {
                loadATrainLine(line);   // 重新写个方法，避免程序堆成金字塔
            }
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

    }

    private void loadATrainLine(String trainLineName){
        // readAllLines腻了，换个方式，用Scanner读，你也可以直接readAllLines
        // 也可以不try，程序把异常throw给调用它的方法
        try (Scanner sc = new Scanner(new File("data\\"+trainLineName + "-stations.data"))) {
            TrainLine trainLine = new TrainLine(trainLineName); // 用参数trainLineName建立trainline对象
            while (sc.hasNext()){   // 用scanner扫描trainline的station信息，每一行是一个站名
                Station station = this.stationMap.get(sc.nextLine());   // 扫回来的是String，所以在这里体现出用map的优越性，直接用站名当key取station
                // station和trainline相互添加
                trainLine.addStation(station);
                station.addTrainLine(trainLine);
            }
            // 把加载好的trainline放入map中
            this.trainLineMap.put(trainLineName, trainLine);
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
    }

    public void loadTrainServicesData(){

    }

    public void listAllStations(){
        // 把所有的station打印出来
        // 这个很简单，map有一个方法是value，可以直接取出所有的value，遍历就行
        UI.println("Method 1:");
        for (Station station : this.stationMap.values()) {
            UI.println(station);
        }

        // 也可以用这种遍历方式，这个比较经典
        UI.println("Method 2:");
        for (String key : this.stationMap.keySet()) {
            UI.println(this.stationMap.get(key));
        }

        // 还可以这样，但本质上还是以上两种方法
        UI.println("Method 3:");
        Collection<Station> stations = this.stationMap.values();
        for (Station station : stations) {
            UI.println(station);
        }
    }

    public void listStationsByName(){
        // byName，官方有所解释，就是按照 alphabetic order，也就是字典序，a>b>c>d>...>z
        // 所以可以直接取出name，对name的数据结构进行排序，然后通过name取station并输出
        List<String> keyList = new ArrayList<>(this.stationMap.keySet());   //set的特点是无序，而我们这个题的关键就是序，所以要用一个有序的数据结构存储key
        Collections.sort(keyList); // 有序不是List的充要条件，List具备有序的条件，但是刚从set中拿出来的元素并不见得有序，所以我们对list排序，这样就确保他是有序的
        UI.println("Method 1:");
        for (String name : keyList) {
            // 这个时候遍历keyList，再通过name去从map中取station，就能得到有序的station
            UI.println(this.stationMap.get(name));
        }

        // 也可以用addAll从set中转移数据到list
        keyList.addAll(this.stationMap.keySet());

        // 不如我们直接给station定义序？接下来的操作可能有点眼花缭乱
        UI.println("Method 3:");
        List<Station> stations = new ArrayList<>(this.stationMap.values());
        stations.sort(new Comparator<Station>() {
            @Override
            public int compare(Station o1, Station o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });
        for (Station station : stations) {
            UI.println(station);
        }
    }

    public void listAllTrainLines(){
        // 这里和station类似，不详写了
        for (TrainLine trainLine : this.trainLineMap.values()) {
            UI.println(trainLine);
        }
    }

    public void listLinesOfStation(String stationName) {
        // 就是给定站点名打印线路信息，很简单，不详写
        Station targetStation = this.stationMap.get(stationName);
        for (TrainLine trainLine : targetStation.getTrainLines()) {
            UI.println(trainLine);
        }
    }

    public void listStationsOnLine(String lineName) {
        // 同给站打线，这是给线打站
        TrainLine targetLine = this.trainLineMap.get(lineName);
        for (Station station : targetLine.getStations()) {
            UI.println(station);
        }
    }

    public void checkConnected(String stationName, String destinationName) {
        Station fromStation = this.stationMap.get(stationName); // 取出起始站
        Station destinationStation = this.stationMap.get(destinationName); // 取出目标站
        for (TrainLine trainLine : fromStation.getTrainLines()) {
            // 要求火车线路方向正确
            // 火车线路的站点信息存在list中，按照火车发车顺序插入，所以我们可以利用这一点
            // 我们取出两个站点在这个火车线路的List中的index，如果起始站的index小于目标站，说明起始站在目标站之前，这样就是方向正确
            int fromIndex = trainLine.getStations().indexOf(fromStation);
            int toIndex = trainLine.getStations().indexOf(destinationStation);
            if (toIndex == -1) continue; //indexOf返回-1,说明list中不存在这个元素，故直接continue进入下一次循环

            if (fromIndex < toIndex) {
                // 方向正确，输出
                UI.println(trainLine);
            }
        }
    }

    public void findNextServices(String stationName, int startTime) {

    }

    public void findTrip(String stationName, String destinationName, int startTime) {

    }
    /*# YOUR CODE HERE */

    private static boolean loadedData = true;  // used to ensure that the program was called from main.

}
